package jp.ize.ux.tween;

// {{{ imports
import haxe.FastList;
import jp.ize.iwp.RuntimeEngine;
import jp.ize.ux.acts.Act;
import flash.events.Event;
import flash.events.EventDispatcher;
// }}}
// {{{ typedef Easing
typedef Easing = Float -> Float -> Float -> Float -> Float
// }}}
// {{{ class UXTween<A,B>
/**
* feffects 1.1.1 をベースにしています。
*/
class UXTween<A,B>
{
	// {{{ properties
	// {{{ public static var aTweens	= new FastList<UXTween<Dynamic,Dynamic>>();
	/**
	* アクティブなトゥイーンのリストです。
	*/
	public static var aTweens	= new FastList<UXTween<Dynamic,Dynamic>>();
	// }}}
	// {{{ public static var aPaused	= new FastList<UXTween<Dynamic,Dynamic>>();
	/**
	* 一時停止中のトゥイーンのリストです。
	*/
	public static var aPaused	= new FastList<UXTween<Dynamic,Dynamic>>();
	// }}}
	// {{{ public var duration	(default, null): Int;
	/**
	* トゥイーンする時間（単位：秒数）指定をします。
	*/
	public var duration	(default, null): Int;
	// }}}
	// {{{ public var position	(default, null): Int;
	/**
	* トゥイーン開始からの時間を保持します。
	*/
	public var position	(default, null): Int;
	// }}}
	// {{{ public var reversed	(default, null): Bool;
	/**
	* 逆トゥイーン中かどうかを保持します。
	*/
	public var reversed	(default, null): Bool;
	// }}}
	// {{{ public var isPlaying(default, null): Bool;
	/**
	* トゥイーン中かどうかを保持します。
	*/
	public var isPlaying(default, null): Bool;
	// }}}
	// {{{ var startTime		: Int;
	/**
	* 開始時間（単位：フレーム）です。
	*/
	var startTime		: Int;
	// }}}
	// {{{ var pauseTime		: Int;
	/**
	* 一時停止した時間（単位：フレーム）を保持します。
	*/
	var pauseTime		: Int;
	// }}}
	// {{{ var getter         : A->B;
	/**
	* 初期値を取得する関数です。
	*/
	var getter         : A->B;
	// }}}
	// {{{ var setter         : A->B->B->Float->Void;
	/**
	* 値を更新するための値設定用の関数です。
	*/
	var setter         : A->B->B->Float->Void;
	// }}}
	// {{{ var curTime        : Int;
	/**
	* 現在の時間を保持します。
	*/
	var curTime        : Int;
	// }}}
	// {{{ var endFunc			: Dynamic->Void;
	/**
	* 終了処理用の関数です。
	*/
	var endFunc			: Dynamic->Void;
	// }}}
	// {{{ var easingF			: Easing;
	/**
	* 値計算用の方程式関数です。
	*/
	var easingF			: Easing;
	// }}}
	// {{{ var obj				: A;
	/**
	* 対象となるオブジェクトです。
	*/
	var obj				: A;
	// }}}
	// {{{ var initobj			: B;
	/**
	* 初期値を保持するオブジェクトです。
	*/
	var initobj			: B;
	// }}}
	// {{{ var endobj			: B;
	/**
	* 終了（目標）値を保持するオブジェクトです。
	*/
	var endobj			: B;
	// }}}
	// {{{ var base			: EventDispatcher;
	/**
	* Event listener のための base オブジェクト
	*/
	var base			: EventDispatcher;
	// }}}
	// }}}
	// {{{ public static function AddTween( tween : UXTween<Dynamic,Dynamic> ) : Void
	/**
	* トゥイーンをアクティブリストに加えます。
	*/
	public static function AddTween( tween : UXTween<Dynamic,Dynamic> ) : Void
	{
		//aTweens.add( tween ) ;
	}

	// }}}
	// {{{ public static function RemoveTween( tween : UXTween<Dynamic,Dynamic> ) : Void
	/**
	* トゥイーンをアクティブリストから削除します。
	*/
	public static function RemoveTween( tween : UXTween<Dynamic,Dynamic> ) : Void
	{
		//if ( tween == null || timer == null )
		if ( tween == null )
			return;
					
		//aTweens.remove( tween );
	}

	// }}}
	// {{{ public static function getActiveTweens() : FastList<Tween>
	/**
	* アクティブなトゥイーンリストを取得します。
	*/
	public static function getActiveTweens() : FastList<UXTween<Dynamic,Dynamic>>
	{
		return aTweens;
	}

	// }}}
	// {{{ public static function getPausedTweens() : FastList<Tween>
	/**
	* 一時停止トゥイーンのリストを取得します。
	*/
	public static function getPausedTweens() : FastList<UXTween<Dynamic,Dynamic>>
	{
		return aPaused;
	}

	// }}}
	// {{{ public static function setTweenPaused( tween : UXTween<Dynamic,Dynamic> ) : Void
	/**
	* トゥイーンをアクティブリストから削除し、一時停止リストに加えます。
	*/
	public static function setTweenPaused( tween : UXTween<Dynamic,Dynamic> ) : Void
	{
		//if ( tween == null || timer == null )
		if ( tween == null )
			return;
					
		//aPaused.add( tween );
		//aTweens.remove( tween );
	}

	// }}}
	// {{{ public static function setTweenActive( tween : UXTween<Dynamic,Dynamic> ) : Void
	/**
	* トゥイーンを一時停止リストから削除し、アクティブリストに加えます。
	*/
	public static function setTweenActive( tween : UXTween<Dynamic,Dynamic> ) : Void
	{
		//if ( tween == null || timer == null )
		if ( tween == null )
			return;
					
		//aTweens.add( tween );
		//aPaused.remove( tween );
	}

	// }}}
	
	// {{{ public function new( obj:A, end:B, getter:A->B, setter:A->B->B->Float->Void, dur:Float, ?easing:Easing )
	/**
	* 対象オブジェクト obj を end で指定する値まで dur 時間（秒数）で easing 関数により
	* トゥイーンするためのオブジェクトを生成します。
	* example.
	*  var sp:Sprite = new Sprite();
	*  var end:{x:Float,y:Float} = {x:200, y:200};
	*  var getter:Sprite->{x:Float,y:Float} = function( obj ) {
	*      return {x:obj.x, y:obj.y};
	*  }
	*  var setter:Sprite->{x:Float,y:Float}->{x:Float,y:Float}->Float->Void =
	*      function( obj, init, end, v ) {
	*          obj.x = init.x+(end.x-init.x)*v;
	*          obj.y = init.y+(end.y-init.y)*v;
	*      };
	*  var easing:Easing = jp.ize.ux.tween.easings.Expo.easeOut;
	*  var tw = new UXTween<Sprite,{x:Float,y:Float}>( sp, end, getter, setter, 10, easing );
	*  tw.start();
	*
	* Create a tween from the [init] value, to the [end] value, while [dur] (in ms)<br />
	* If [obj] & [prop] set, Reflect is used to apply the value to the obj's param
	* There is a default easing equation.
	*/
	public function new( obj:A, end:B, getter:A->B, setter:A->B->B->Float->Void, dur:Float, ?easing:Easing )
	{
		this.getter = getter;
		this.setter = setter;
		duration = Math.round(dur*20);
		this.obj = obj;
		this.initobj = null;
		this.endobj = end;
		if ( easing != null )
			easingF = easing;
		else
			easingF = easingEquation;
		isPlaying = false;
		curTime = 0;
	}

	// }}}
	// {{{ public function start( ?b:EventDispatcher ) : Void
	/**
	* このトゥイーンを開始します。
	*/
	public function start( ?b:EventDispatcher ) : Void
	{
		initobj = getter(obj);
		startTime = 0;
		
		if ( duration == 0 )
			endTween();
		//else
		//	UXTween.AddTween( this );
		isPlaying = true;
		position = 0;
		reversed = false;

		this.base = b;

		if ( Std.is(obj,EventDispatcher) ) {
			cast(obj,EventDispatcher).addEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else if ( base!=null ) {
			base.addEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else {
			RuntimeEngine.getStage().addEventListener( Event.ENTER_FRAME, onEnterFrame );
		}
	}

	// }}}
	// {{{ public function pause() : Void
	/**
	* このトゥイーンを一時停止させます。
	*/
	public function pause() : Void
	{
		if ( Std.is(obj,EventDispatcher) ) {
			cast(obj,EventDispatcher).removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else if ( base!=null ) {
			base.removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else {
			RuntimeEngine.getStage().removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		}
		UXTween.setTweenPaused( this );
		isPlaying = false;
	}

	// }}}
	// {{{ public function resume() : Void
	/**
	* 一時停止しているこのトゥイーンを再開します。
	*/
	public function resume() : Void
	{
		if ( Std.is(obj,EventDispatcher) ) {
			cast(obj,EventDispatcher).addEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else if ( base!=null ) {
			base.addEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else {
			RuntimeEngine.getStage().addEventListener( Event.ENTER_FRAME, onEnterFrame );
		}
		UXTween.setTweenActive( this );
		isPlaying = true;
	}

	// }}}
	// {{{ public function seek( ms : Int ) : Void
	/**
	* 指定した時間に移動します。
	* Go to the specified position [ms] (in ms) 
	*/
	public function seek( ms : Int ) : Void
	{
		curTime = ms;
	}

	// }}}
	// {{{ public function reverse() : Void
	/**
	* 現在の時間から逆トゥイーンを開始します。
	* Reverse the tweeen from the current position 
	*/
	public function reverse() : Void
	{
		reversed = !reversed;
	}

	// }}}
	// {{{ public function stop() : Void
	/**
	* トゥイーンを停止させます。
	*/
	public function stop() : Void
	{
		if ( Std.is(obj,EventDispatcher) ) {
			cast(obj,EventDispatcher).removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else if ( base!=null ) {
			base.removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else {
			RuntimeEngine.getStage().removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		}
		UXTween.RemoveTween( this );
		isPlaying = false;
	}

	// }}}
	// {{{ inline public function onEnterFrame( e:Dynamic ) : Void
	/**
	* フレーム毎に呼ばれるトゥイーン用関数です。
	*/
	inline public function onEnterFrame( e:Dynamic ) : Void
	{
		var dir:Int = 1;
		if ( reversed ) {
			dir = -1;
		}
		curTime += dir;
		
		var curVal = getCurVal( curTime );
		setter( obj, initobj, endobj, curVal );
		if ( curTime >= duration || curTime <= 0 ) {
			endTween();
		}
		position = curTime;						
	}

	// }}}
	// {{{ inline function getCurVal( curTime : Int ) : Float
	/**
	* 方程式関数を使って現在時間から値を計算します。
	*/
	inline function getCurVal( curTime : Int ) : Float
	{
		//return easingF( curTime, initVal, endVal - initVal, duration );
		return easingF( curTime, 0.0, 1.0, duration );
	}

	// }}}
	// {{{ function endTween() : Void
	/**
	* トゥイーンの終了処理です。終了処理関数が設定されていた場合実行します。
	*/
	function endTween() : Void
	{
		RemoveTween( this );
		
		if ( endFunc != null )
			endFunc( obj );
		if ( Std.is(obj,EventDispatcher) ) {
			cast(obj,EventDispatcher).removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else if ( base!=null ) {
			base.removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		} else {
			RuntimeEngine.getStage().removeEventListener( Event.ENTER_FRAME, onEnterFrame );
		}
	}

	// }}}
	// {{{ public function setEndTweenHandler( end : Dynamic -> Void ) : Void
	/**
	* トゥイーン終了時用の処理関数を設定します。対象オブジェクトを引数にとります
	* Set the [update] event when the value is tweening, and the [end] value when it finished
	*/
	public function setEndTweenHandler( end : Dynamic -> Void ) : Void
	{
		endFunc = end;
	}

	// }}}
	// {{{ public function getEndTweenHandler() : Dynamic -> Void
	/**
	* トゥイーン終了時用の処理関数を取得します。
	* Get the [end] value when it finished
	*/
	public function getEndTweenHandler() : Dynamic -> Void
	{
		return endFunc;
	}

	// }}}
	// {{{ public function setEasing( easingFunc : Easing ) : Void
	/**
	* トゥイーンのための方程式関数を設定します。
	* Set the [easingFunc] equation to use for tweening
	*/
	public function setEasing( easingFunc : Easing ) : Void
	{
		if ( easingFunc != null )
			easingF = easingFunc;
	}

	// }}}
	// {{{ inline function easingEquation( t : Float, b : Float, c : Float, d : Float ) : Float
	/**
	* Easingが指定されなかった場合のデフォルトのEasingです。
	*/
	inline function easingEquation( t : Float, b : Float, c : Float, d : Float ) : Float
	{
		return c / 2 * ( Math.sin( Math.PI * ( t / d - 0.5 ) ) + 1 ) + b;
	}

	// }}}
}
// }}}
